/** 
 * @(#)Node.java
 */

/**
 * @author Robert Denda&Didier Gutacker
 * @version 1.1
 * @see Knoten
 * @see DVMRPRoutingTable
 * @see PeriodicSender 
 * @see Network
 * @see Link 
 *
 * original author Robert Denda, changes made by Didier Gutacker
 */


import java.awt.*;
import java.util.*;

public class Node extends Knoten {
 
  private DVMRPRoutingTable routingTable;
  
 

  protected int multicastSenderID = -1;
  private PeriodicSender sender;
  private boolean pruned = false;
  private boolean sending = false;
  private int npackets = 0;
  private int tdpkts =0;
  private int spackets=0;//to know the no.of packets generated by node as source
 

  public Node( int id, Network net, Graphics gc, int xpos, int ypos, int width, int height ) {
    super( id, net, gc, xpos, ypos, width, height );
    routingTable = new DVMRPRoutingTable( id );
    ResultMap.m[id] = new HashMap<Integer,Integer>();
    
    
    if ( ! net.isFinished() ) {
      net.writeln( text.nodeTitleText + " " + id + " " + text.addedText );
    } // if
    tracker = new MediaTracker( net.getCanvas() );
    tracker.addImage( nodeImage, 1 );
    try{ 
      tracker.waitForID( 1 ); 
    } catch(InterruptedException e){
    } // try;
    if( tracker.isErrorID(1) ){
      System.err.println( "Error:  could not load \"router.gif\".");
    } // if 
  } // Node.Node
  
  /**
   * Weckt die zuvor mit suspend() schlafengelegten Thread wieder auf
   *
   * @see Node#suspend
   */
  
  public void resume() {
    super.resume();
    if ( sender != null ) {
      sender.resume();
    } // if
  } // ArchitectureComponent.resume

  /**
   * Legt den objekteigenen Thread und den eventuell existierenden Sender schlafen
   *
   * @see Node#resume
   */

  public void suspend() {
    if ( sender != null ) {
      sender.suspend();
    } // if
    super.suspend();
  } // ArchitectureComponent.suspend

  /*
   * This is the source node which sends the multicast packets to all other neighbors (links)
   * in the routing table which are nothing but shortest path links
   * Find the max. of all dvr entries here in the source and get the delay to reach each neighbor
   * and check packet.pseudo diameter > delay to reach neighbor then subtract the delay from 
   * packet.pseudo diameter and forward it to all neighbor links of the node.
   */
  public void sendMulticastPackets() {                              
    Vector links = routingTable.getLinks();
   // ResultMap.m[this.id].clear(); //clearing the entries (that column of src id) for each call of start sending
    //Finding the maximum at the source..
     int max = 0,pd = 0,ten=0;
     max = getMaxDiameter();
     pd  = max;
    //((RoutingEntry)routingTable.entries.elementAt(i)).metric
    for ( int i = 0; i < links.size(); i += 1 ) {
      Link l = (Link) links.elementAt( i );
      int nextHopID = routingTable.getNextHopRouter( l );
      if ( routingTable.getPrunedFlag( nextHopID ) ) {
	       routingTable.increasePrunedTimer( nextHopID );
	       continue;
      } // if
      pd = max;
      if (pd >= l.getWeight() ){
    	  pd = pd - l.getWeight();
          l.sendMulticastPacket( this.id, this, pd );
          mpktCounter(this.id,this.id);
          this.spackets++;   //No.of packets generating at source
      }
      ten=ResultMap.m[this.id].get(new Integer(this.id));
      if(ten % 10 == 0  )
      {
      	printMap(this.id);
      }
    } // for
    	
    System.out.println("Node "+ this.id + " as source generated " + this.spackets + " packets at source");
    System.out.println("Node "+ this.id + " sent " + this.tdpkts + "  packets during dvr exhange");
  } // Node.sendMulticastPackets

  public void finishUp() {
    ((Network)net).writeln( text.nodeTitleText + " " + id + " " + text.removedText );
    attachedLinksGuard.acquire();
    for ( int i = attachedLinks.size() - 1; ; i = attachedLinks.size() - 1) { // trick:  disconnect will remove links
      if ( i < 0 ) {
	break;
      } // if
     ((Network) net).removeLink( ( (Link) attachedLinks.elementAt( i ) ) );
    } // for
    attachedLinks = null;
    attachedLinksGuard.release();
    super.finishUp();
    routingTable = null;
    nodeImage = null;
    fontMetrics = null;
    if ( sender != null ) {
      stopSending();
      sender.finishUp();
      sender = null;
    } // if
  } // Node.finishUp

  synchronized public void paint() {
    if ( finished ) {
      return;
    } // if
    while ( !initialized ) {
      Thread.yield();
    } // while

    absXPos = xpos + net.getXOffset();
    absYPos = ypos + net.getYOffset();
    gc.drawImage( nodeImage, absXPos, absYPos + textHeight, width, height - textHeight, this );
    if ( marked ) {
      gc.setColor( Color.red );
      gc.drawRect( absXPos, absYPos, width - 1, height - 1 );
    } else if ( sending ) {
      gc.setColor( Color.magenta );
    } else if ( pruned ) {
      gc.setColor( Color.green );
      gc.drawRect( absXPos, absYPos, width - 1, height - 1 );
    } else {
      gc.setColor( Color.black );
    } // if
    String t = text.nodeTitleText + " " + id;
    gc.drawString( t, absXPos + ( ( width - fontMetrics.stringWidth( t ) )
				  / 2 ), absYPos + fontMetrics.getAscent() );
  } // Node.paint

  public void connect( int nodeID, int metric, Link l ) {
    if ( ! net.isFinished() ) {
      ((Network)net).acquireTextLock();
     ((Network) net).write( text.nodeTitleText + " " + id + " " + text.nodeConnectText + " " + text.linkTitleText +" "+ l.getID() + " " );
      if ( attachedLinks.size() > 0 ) {
	((Network)net).writeln( text.nodeConnectAdditionalText1 + " " +
		     text.routingUpdatePacketText + " " +
		     text.andText + " " + text.aText + " " +
		     text.routingTableRequestPacketText + " " +
		     text.nodeConnectAdditionalText2 );
      } else {
	((Network)net).writeln( "" );
      } // if
      ((Network)net).releaseTextLock();
    } // if
    int infinity = ((Network)net).getInfinity();
    Vector v = new Vector();
    RoutingEntry entry = new RoutingEntry( id, 0, l, infinity );
    v.addElement( entry );
    l.sendRoutingTableUpdate( this, v );
    sharedv.acquire();
    this.tdpkts++;
    dpktCounter(this.id);
    sharedv.release();
    /*
     * unnecessary?? request?? 
     */
  //  l.sendRoutingTableRequest( this );
    
    v = new Vector();
    entry = new RoutingEntry( nodeID, metric, l, infinity );
    v.addElement( entry );
    attachedLinksGuard.acquire();
    for ( int i = 0; i < attachedLinks.size(); i += 1 ) {
      ( (Link) attachedLinks.elementAt( i ) ).sendRoutingTableUpdate( this, v );
       
        this.tdpkts++;
        dpktCounter(this.id);
        
       /*
        * unnecessary request ??
        */
        if(l.setWeightRequest  && !((Link) attachedLinks.elementAt( i )).equals(l) )
        {
            ( (Link) attachedLinks.elementAt( i ) ).sendRoutingTableRequest( this );
             this.tdpkts++;
             dpktCounter(this.id);
        }     
      
      
    } // for
    attachedLinks.addElement( l );
    attachedLinksGuard.release();
  } // Node.connect

  public void disconnect( Link l ) {
    int infinity = ((Network)net).getInfinity(); // ???????????
    attachedLinksGuard.acquire();
    routingTable.deregisterLink( l,infinity );
    attachedLinks.removeElement( l );
    
    Vector entries = routingTable.getEntries();
    Vector outputEntries = new Vector();
    
    if ( ! net.isFinished() ) {
      for ( int i = 0; i < attachedLinks.size(); i += 1 ) {
    	  for ( int k = 0; k < entries.size(); k += 1 ) {
    	      RoutingEntry entry = (RoutingEntry) entries.elementAt( k );
    	      //int infinity = ((Network)net).getInfinity();
    	     // if ( ! entry.l.equals( l ) )
    	      outputEntries.addElement( new RoutingEntry( entry.destinationID, entry.metric, entry.l, infinity ) );
    	      
    	      
    	  }
    	  if ( ! outputEntries.isEmpty() ) { 
    	    	
    		  ( (Link) attachedLinks.elementAt( i ) ).sendRoutingTableUpdate( this, outputEntries );
    		      
    		      this.tdpkts++;
    		      dpktCounter(this.id);
    		    
    		    } // if
	    //sendRoutingEntries( (Link) attachedLinks.elementAt( i ) );
	   // ( (Link) attachedLinks.elementAt( i ) ).sendRoutingTableRequest( this );
	    
      } // for
    } // if
    attachedLinksGuard.release();
  } // Node.disconnect

  public void processRoutingTableUpdate( Link l, Vector entries ) {
    if ( finished ) {
      return;
    } // if
    Vector updatedEntries = new Vector();
    Vector poisonedUpdatedEntries = new Vector();
    boolean routingTableRequested = false;
    for ( int i = 0; i < entries.size(); i += 1 ) {
      RoutingEntry entry = (RoutingEntry) entries.elementAt( i );
      int metric = entry.metric;
      int infinity = entry.infinity;
      int nodeID = entry.destinationID;
      if ( ! net.isFinished() ) {
	       ((Network)net).acquireTextLock();
	       ((Network)net).write( text.nodeTitleText + " " + id + " " + text.processRoutingTableUpdateText + " " + text.nodeTitleText + " " + nodeID + " " + text.viaText + " " + text.linkTitleText + " " + l.getID() + " " + text.withMetricText + " " );
	       if ( metric == infinity ) {
	            ((Network)net).writeln( "\"infinity\"" );
	  
	        // ???
	       }else if ( metric > infinity ) {
	            ((Network)net).writeln( "\"infinity\" + " + ( metric - infinity + l.getWeight() ) + "\n + (poison reverse)" );
	       } else {
	               ((Network)net).writeln( "" + ( metric + l.getWeight() ) );
	       } // if
	           ((Network)net).releaseTextLock();
      } // end if ( ! net.isFinished() )

      if ( nodeID != id ) {  
	       if ( routingTable.update( l, nodeID, metric, (Link) entry.l, infinity ) ) {
	           if ( ! net.isFinished() ) {
	                ((Network)net).acquireTextLock();
	                ((Network)net).write( text.nodeTitleText + " " + id + " " +
		            text.routingTableUpdateText + " " );
	                if ( attachedLinks.size() > 1 ) {
	                     ((Network)net).writeln( text.nodeConnectAdditionalText1 + " " +
			             text.routingUpdatePacketText + " " +
			             text.nodeConnectAdditionalText2 );
	                }  
	                else {
	                      ((Network)net).writeln( "" );
	                     } // if
	                 ((Network)net).releaseTextLock();
	            } // if
	          RoutingEntry updatedEntry = routingTable.lookUp( nodeID );
	          
	          /*
	           * this should be the one to reduce the frequency of 
	           * routing table request packets not **solely** used for the
	           * purpose of identification of child links. ???
	           */
	          if ( updatedEntry.infinity == updatedEntry.metric ) {
	             routingTableRequested = true;
	          } // if
	          RoutingEntry newEntry = new RoutingEntry( updatedEntry.destinationID, updatedEntry.metric, updatedEntry.l, updatedEntry.infinity );
	          updatedEntries.addElement( newEntry );   
	          
	         
	         
	          /** this should be the one to deal the count to infinity problem..
	           * 
	           * if the router is on the same line to forward packets to
    	       * router 'X'   from router 'Y'  then send infinity + metric
	           */
	          RoutingEntry poisonedEntry = new RoutingEntry( updatedEntry.destinationID, updatedEntry.metric + updatedEntry.infinity, updatedEntry.l, updatedEntry.infinity );
	          poisonedUpdatedEntries.addElement( poisonedEntry );
	          
	      } // end  if ( routingTable.update ( l, nodeID, metric, (Link) entry.l, infinity ) )
      } //end if ( nodeID != id )
       else {  //why it is throwing away the packet???
    	  /*  since it is getting the route to it  from itself
    	   * 
    	   */
	             if ( ! net.isFinished() ) {
	                    ((Network)net).writeln( text.nodeTitleText + " " + id + " " + text.nodeThrowAwayPacketText + " " +text.routingUpdatePacketText + " " );
	             } // end  if ( ! net.isFinished() )
            } // end else if ( nodeID != id )
    } // for
    
   
    
  /*   if ( !poisonedUpdatedEntries.isEmpty() ) {
      l.sendRoutingTableUpdate( this, poisonedUpdatedEntries );
      sharedv.acquire();
      this.tdpkts++;
      dpktCounter(this.id);
      sharedv.release();
    } // if */
    if ( !updatedEntries.isEmpty() ) {
      attachedLinksGuard.acquire();
     
      /* 
       * After updating the required entry ,add the remaining entries along 
       * with the updated entry in this router's routing table to be sent to 
       * the other router  which may not be knowing to the other router which 
       * sent the update to this router 
       */
      Vector tentries = routingTable.getEntries();
      for(int t=0; t<tentries.size();t++)
      {  
    	  int flag=0;
    	  RoutingEntry tentry = (RoutingEntry) tentries.elementAt( t );
    	  for(int m=0; m<updatedEntries.size(); m++)
    	  {
    		  RoutingEntry re = (RoutingEntry)updatedEntries.elementAt(m);
    		if(re.destinationID == tentry.destinationID)
    		{
    			flag=1;
    			break;
    		}		
    	  }
    	  if(flag == 1)  //flag==1
    		continue;
    	  updatedEntries.addElement(tentry);
    	  
      }  
      
      for ( int i = 0; i < attachedLinks.size(); i += 1 ) {
    	  
    	   /*
    	    * send only to those links which are not incoming links(on which this router received an update)
    	    */
	    //  if ( !( (Link) attachedLinks.elementAt( i ) ).equals( l ) ) //!( (Link) attachedLinks.elementAt( i ) ).equals( l )
	    //  {
	          ( (Link) attachedLinks.elementAt( i ) ).sendRoutingTableUpdate( this, updatedEntries );
	         
	          this.tdpkts++;
	          dpktCounter(this.id);
	    //  }    
	          
	        // if
	      if ( routingTableRequested ) {
	         ( (Link) attachedLinks.elementAt( i ) ).sendRoutingTableRequest( this );
	         this.tdpkts++;
	          dpktCounter(this.id);
	      } // if
      } // for
      
     
      attachedLinksGuard.release();
    } //end  if ( !updatedEntries.isEmpty() )
    
    
    
    
  } // Node.processRoutingTableUpdate

  public void sendRoutingEntries( Link l ) {
    if ( finished ) {
      return;
    } // if
     
    Vector entries = routingTable.getEntries();
    Vector outputEntries = new Vector();

    for ( int i = 0; i < entries.size(); i += 1 ) {
      RoutingEntry entry = (RoutingEntry) entries.elementAt( i );
      
      // split horizon processing
      int infinity = ((Network)net).getInfinity();
   //   if ( entry.l.equals( l ) ) { //????????
    	 /*  
    	  * 
    	  * if downstream(this) router selects an upstream router as the best next hop
    	  * (link l in this node's routing table) for each source network i.e., for
    	  * each particular destination id(source)  in its routing table then echo back
    	  * the route with metric + infinity to indicate it's  dependency on the 
    	  * upstream interface for that particular destination(if it becomes source)
    	  *  (here link "l" on which it received  the  routing table request packet) 
    	  */
    	  
	 //      outputEntries.addElement( new RoutingEntry( entry.destinationID, infinity + entry.metric, entry.l, infinity ) );
   //   } else {
	              outputEntries.addElement( new RoutingEntry( entry.destinationID, entry.metric, entry.l, infinity ) );
     // } // if
    } // for
    if ( ! outputEntries.isEmpty() ) { 
    	
      l.sendRoutingTableUpdate( this, outputEntries );
      this.tdpkts++ ;
      dpktCounter(this.id);
       
      
      
    } // if
  } // Node.sendRoutingEntries

  public final void forwardPacket( int nodeID, Link l, int pd ) {
    multicastSenderID = nodeID;
    
    if ( this.id == nodeID ) {
      return;
    } // if
    if ( ! l.equals( routingTable.lookUp( nodeID ).l ) ) { 
    	/* if(pruned)
    	{   	
    		sendPruningMessage(l);
    	}*/	
    	 //this.npackets ++;  //No.of rejected or duplicate packets 
    	// this.npackets ++; //adding pruning message packets to duplicates
    	// System.out.println("Node "+ this.id + " has  " + this.npackets + " duplicate packets   ");
    	    
    	  return;
      //forward from shortest path only  , yes it is a
      //RPF check  where nodeID is the source  where the packet has come from.. 
                                                                   
    } // if
  //   Vector links = routingTable.getForwardInterfaces( nodeID );
    Vector links = routingTable.getUnprunedLinks();
    //this.npackets ++;  //No.of accepted packets before forwarding
   
    /*
     * get here all neighbor links except the link on which it received the packet
     * get the maximum of all dvr entries here and check with the packets pseudo diameter
     * field if the packets pd > max of all dvr entries then get the delay to reach 
     * each neighbor from dvr table and subtract it from packets pd and forward the
     * packet  to all  neighboring links.
     *        else discard the packet (don't forward it).
     * 
     */
    
    
    /*
     * This is the logic for other version of our idea that our professor told ...06/20/08 
     */
     int max  = getMaxDiameter();
     if ( max < pd)
     {
    	 pd = max;
     }
     
     int tpd = pd;
     int pflag=0; //to know if the link is still in pruning state,since if the link is pruned there will be no links in getUnprunedlinks()
     
    if ( links != null ) {
      for ( int i = 0; i < links.size(); i += 1 ) {
    	  Link neighbor = (Link) links.elementAt( i );
    	  if (  neighbor.equals( l )   )  // || tiebreak(neighbor)  || neighbor.tiebreak == true 
    	  {
    		  continue;
    	  }
    	  
    	  if (pd >= neighbor.getWeight() ){
        	  pd = pd - neighbor.getWeight();
              neighbor.sendMulticastPacket( nodeID, this, pd );
              pflag=1;
              this.npackets ++;
              mpktCounter(nodeID,this.id);
              System.out.println("Node "+ this.id + " generated  " + this.npackets + "  packets   ");
                            
          }
    	  pd = tpd; // to keep correct value of pd in the next iteration
	                                                                         // ((Link) links.elementAt( i )).sendMulticastPacket( nodeID, this );
      } // for
    } // if
    if ( pflag == 0 ) {
      if ( pruned ) {
	sendPruningMessage();
      } // if
    } // if 
    
   
    

  } // Node.forwardPacket 

  synchronized public void startSending() {
    if ( !sending ) {
      ResultMap.m[this.id].clear(); //clearing the entries (that column of src id) for each call of start sending
      //printMap(this.id);
      sender = new PeriodicSender( this );
      sending = true;
    } // if
  } // Node.startSending

  synchronized public void stopSending() {
    if ( sending ) {
      sending = false;
      sender.stop();
      paint();
    } // if
  } // Node.stopSending
  
  public void sendPruningMessage() {
	    if ( multicastSenderID == -1 ) {
	      return;
	    } // if
	    RoutingEntry entry = routingTable.lookUp( multicastSenderID );
	   // Vector interfaces = routingTable.getForwardInterfaces( multicastSenderID );
	     
	    if ( entry != null ) {
	     // if ( links == null || links.size() == 0 ) {
		  ((Link)entry.l).sendPruningMessage( this );
		   routingTable.unpruneEntries();
	     // } // if
	      ((Network)net).increasePrunedNodes();
	      pruned = true;
	      paintRequest();
	    } // if
	  } // Node.sendPruningMessage

  public void sendPruningMessage(Link l) {      //to handle duplicate packets
    if ( multicastSenderID == -1 ) {
      return;
    } // if
   
	   l.sendPruningMessage( this );
	   paintRequest();
   
  } // Node.sendPruningMessage

  public void processPruningMessage( int nodeID, Link l ) {
    if ( ! net.isFinished() ) {
      ((Network)net).writeln( text.nodeTitleText + " " + id + " " + text.receivedPruningPacketText + " " + text.viaText + " " + text.linkTitleText + " " + l.getID());
    } // if
    routingTable.setPrunedFlag( nodeID );
    if ( pruned ) {
      sendPruningMessage();	// if necessary, propagate pruning message
    } // if 
  } // Node.processPruningMessage

  public void sendGraftingMessage() {
    if ( pruned ) {
      RoutingEntry entry = routingTable.lookUp( multicastSenderID );
      if ( entry != null ) {
	((Link)entry.l).sendGraftingMessage( this );
	((Network)net).decreasePrunedNodes();
	pruned = false;
	gc.clearRect( absXPos, absYPos, width, height );
	paintRequest();
      } // if
    } // if
  } // Node.sendGraftingMessage

  public void processGraftingMessage( int nodeID, Link l ) {
    if ( ! net.isFinished() ) {
      ((Network)net).writeln( text.nodeTitleText + " " + id + " " + text.receivedGraftingPacketText + " " + text.viaText + " " + text.linkTitleText + " " + l.getID() );
    } // if
    if ( routingTable.getPrunedFlag( nodeID ) ) {
      routingTable.unsetPrunedFlag( nodeID );
    } // if
    l.sendGraftingAck( this );
    RoutingEntry entry = routingTable.lookUp( multicastSenderID );
    if ( entry != null ) {
      ((Link)entry.l).sendGraftingMessage( this );
    } // if
  } // Node.processGraftingMessage

  public void processGraftingAck( int nodeID, Link l ) {
    if ( ! net.isFinished() ) {
      ((Network)net).writeln( text.nodeTitleText + " " + id + " " + text.receivedGraftingPacketAckText + " " + text.viaText + " " + text.linkTitleText + " " + l.getID());
    } // if 
  } // Node.processGraftingAck

  public void inspectTable() {
    routingTable.createTableWindow( this, (Network)net );
  } // Node.inspectTable
  
  //To find pseudo diameter
  public int getMaxDiameter() {
	  int temp=0;
	  
	     if ( routingTable.entries.size() != 0 )
	     {
	    	 temp = ((RoutingEntry)routingTable.entries.elementAt(0)).metric;
	     
	     
	          for (int i=1;i < routingTable.entries.size();i+=1)
	          {
	    	      if(((RoutingEntry)routingTable.entries.elementAt(i)).metric > temp)
	    	       {
	                  temp = ((RoutingEntry)routingTable.entries.elementAt(i)).metric;   		 
	    	       }
	          }
	     }
	     return temp;
  }
  
  
  public void mpktCounter(int srcid,int nid)
  {
	   Integer cnt;
	  cnt = (Integer)ResultMap.m[srcid].get(new Integer(nid));
		
		if(cnt == null)
			ResultMap.m[srcid].put(new Integer(nid), new Integer(1));
		else
			ResultMap.m[srcid].put(new Integer(nid), new Integer(cnt.intValue() +1));
		
  }
  public void dpktCounter(int nid)
  {
	   Integer cnt;
	  cnt = (Integer)ResultMap.dvr.get(new Integer(nid));
		
		if(cnt == null)
			ResultMap.dvr.put(new Integer(nid), new Integer(1));
		else
			ResultMap.dvr.put(new Integer(nid), new Integer(cnt.intValue() +1));
		
  }
  public void printMap(int srcid)
  {
	  System.out.println("\n The multicast pkts generated at each node when src node gen 10mpkts");
	  System.out.println(ResultMap.m[srcid].toString());
	  int sum=0;
	  for (Iterator<Map.Entry<Integer, Integer>> iter = ResultMap.m[srcid].entrySet().iterator();iter.hasNext();)
	  {
			Map.Entry<Integer, Integer> entry= (Map.Entry<Integer,Integer>)iter.next();
			sum += entry.getValue(); 
	  }
	  System.out.println("The sum of mpkts when node"+srcid+" acts as source is "+sum );
		
	  System.out.println("\nThe dvr pkts generated at each node: ");
	  System.out.println(ResultMap.dvr.toString());
	  
	  sum=0;
	  for (Iterator<Map.Entry<Integer, Integer>> iter = ResultMap.dvr.entrySet().iterator();iter.hasNext();)
	  {
		    Map.Entry<Integer, Integer> entry= (Map.Entry<Integer,Integer>)iter.next();
			sum += entry.getValue(); 
	  }
		System.out.println("The sum of dvr pkts of all nodes is "+sum +"\n" );
		  
	  
  }
//To find tiebreak condition
  public boolean tiebreak(Link l) {
	  for (int i=0;i< l.tiebreaknode.size();i++)
	  {
		  if (l.tiebreaknode.elementAt(i).equals(this.id) && 
				   l.tiebreaklink.elementAt(i).equals(l)) {
			     return true;
		  }
	  }
  	  return false;   
  } //tiebreak
  
} // Node







